﻿/*
* This file is part of WinMergeFS.
*
* WinMergeFS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.

* WinMergeFS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with WinMergeFS.  If not, see <http://www.gnu.org/licenses/>.
*/

using System;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Dokan;
using WinMergeFS.Common;
using RealtimeTrace;
using Microsoft.Win32.SafeHandles;

namespace WinMergeFS.API
{
    /// <summary>
    /// Provides Dokan filesystem operations.
    /// </summary>
    internal class FSOperations : DokanOperations
    {
        private ulong _count;

        private Profile _profile;
        private RootSelectManager _rootselectmanager;
        private RootSelectHelper _rootselecthelper;
        private LoggerEventHandler _logger;
        private List<FileSystemWatcher> fsw;
        private Dictionary<string, ArrayList> _cache;
        private Dictionary<string, FileInformation> _cache_files;
        private Object _cache_lock;
        private Object _cache_files_lock;
        private Dictionary<ulong, FileStream> _handles;

        public FSOperations(Profile Profile, LoggerEventHandler Logger)
        {
            try
            {
                _profile = Profile;
                _logger = Logger;
                _rootselectmanager = new RootSelectManager(_profile, _logger);
                _rootselecthelper = new RootSelectHelper(_profile);
                _logger("FSOp: Creating class", LogLevel.debug);
                fsw = new List<FileSystemWatcher>();
                for (int i = 0; i < Profile.Roots.Count; i++)
                {
                    fsw.Add(new FileSystemWatcher(Profile.Roots[i].RootPath));
                    fsw[i].Changed += new FileSystemEventHandler(FSOperations_Changed);
                    fsw[i].Created += new FileSystemEventHandler(FSOperations_Created);
                    fsw[i].Deleted += new FileSystemEventHandler(FSOperations_Deleted);
                    fsw[i].Renamed += new RenamedEventHandler(FSOperations_Renamed);
                    fsw[i].EnableRaisingEvents = true;
                }
                if (_cache_lock == null) { _cache_lock = new Object(); }
                if (_cache_files_lock == null) { _cache_files_lock = new Object(); }
                if (_cache == null) { lock (_cache_lock) { _cache = new Dictionary<string, ArrayList>(); } }
                if (_cache_files == null) { lock (_cache_files_lock) { _cache_files = new Dictionary<string, FileInformation>(); } }
                _logger("FSOp: Cache initialised", LogLevel.debug);
                _count = 0;
                _handles = new Dictionary<ulong, FileStream>();


            }
            catch (Exception ex)
            { TraceFactory.InsertTrace(this, ex, Profile, Logger); throw; }
        }

        private void ClearCache()
        {
            _logger("FSOp: Filesystem change detected, clearing cache.", LogLevel.debug);
            lock (_cache_lock) { _cache.Clear(); }
            lock (_cache_files_lock) { _cache_files.Clear(); }
        }

        void FSOperations_Renamed(object sender, RenamedEventArgs e)
        {
            ClearCache();
        }

        void FSOperations_Deleted(object sender, FileSystemEventArgs e)
        {
            ClearCache();
        }

        void FSOperations_Created(object sender, FileSystemEventArgs e)
        {
            ClearCache();
        }

        void FSOperations_Changed(object sender, FileSystemEventArgs e)
        {
            ClearCache();
        }

        public int CreateFile(String filename, FileAccess access, FileShare share,
            FileMode mode, FileOptions options, DokanFileInfo info)
        {
            _logger("FSOp: CreateFile: FN: " + filename + ", Access: " + access + ", Share: " + share + ", Mode: " + mode + ", IsDirectoy: " + info.IsDirectory, LogLevel.debug);
            try
            {
                info.Context = null;


                // emtpy string, lolwut!??!
                if (filename.Equals("")) return 0;

                // its is a directory, set IsDirectory to TRUE and return
                if (_rootselecthelper.GetDirectoryRoot(filename) >= 0)
                {
                    info.IsDirectory = true;
                    _logger("                  <-- 0, IsDirectory", LogLevel.debug);
                    return 0;
                }

                // it is probabaly a file

                // check if file exists, first
                int existingroot = _rootselecthelper.GetFileRoot(filename);

                // by default we want to use the root of the existing file
                bool createnewfileusingrootselection = false;


                if (mode == FileMode.CreateNew)
                {
                    // if file already exists and we are trying a create a new one, we MUST return an error, else files are being overwritten witout question!!
                    if (existingroot >= 0) { _logger("                  <-- ERROR_PATH_NOT_FOUND", LogLevel.debug); return -DokanNet.ERROR_ALREADY_EXISTS; }
                    else createnewfileusingrootselection = true;
                }
                else if (mode == FileMode.Create)
                { // on create we have to create a new file in any case
                    if (existingroot < 0) createnewfileusingrootselection = true;  // we are creating new file, else overwrite
                }
                else if (mode == FileMode.OpenOrCreate)
                {
                    //if we want to open a file and it doesnt exist, we have to create it first
                    if (_rootselecthelper.GetFileRoot(filename) < 0) createnewfileusingrootselection = true;
                }

                string finalfilepath;
                if (createnewfileusingrootselection)
                {
                    // we maybe need some sub dirs on the selected root
                    int target = _rootselectmanager.GetRootForCreateOP(filename);
                    if (target < 0) { return -DokanNet.ERROR_ACCESS_DENIED; }
                    CreateNeededSubDirs(filename, target);
                    finalfilepath = _profile.Roots[target].RootPath + filename;
                }
                else
                {
                    if (existingroot < 0)
                    {
                        // file does not exist, this part is important, because it is called before Move File!
                        // if any root has the partent folder, we return FILE_NOT_FOUND, else we return PATH_NOT_FOUND
                        // this asures that MoveFile can move file x to PathY\x, even if the folder PathY only exists on another root!

                        string filepath = filename.Remove(filename.LastIndexOf("\\"));
                        for (int i = 0; i < _profile.Roots.Count; i++)
                        {
                            if (_rootselecthelper.GetDirectoryRoot(filepath) >= 0) { _logger("                  <-- ERROR_FILE_NOT_FOUND", LogLevel.debug); return -DokanNet.ERROR_FILE_NOT_FOUND; }
                        }
                        _logger("                  <-- ERROR_PATH_NOT_FOUND", LogLevel.debug);
                        return -DokanNet.ERROR_PATH_NOT_FOUND;
                    }
                    else finalfilepath = _rootselecthelper.GetFilePath(filename, existingroot);
                }

                _logger("                  --> " + finalfilepath, LogLevel.debug);

                // we let File.Open manage any of the actualy CREATE operations
                // we have to override FileAcces and FileShare to avoid errors, no idea why
                FileStream asdf = File.Open(finalfilepath, mode, FileAccess.ReadWrite, FileShare.Delete | FileShare.ReadWrite);
                info.Context = ++_count;
                _handles.Add(_count, asdf);

                // after creating a new file we have to clear the cache
                if (createnewfileusingrootselection) ClearCache();
                _logger("                  <-- 0, opened", LogLevel.debug);
                return 0;
            }
            catch (System.IO.DirectoryNotFoundException) { _logger("                  <-- ERROR_PATH_NOT_FOUND", LogLevel.debug); return -DokanNet.ERROR_PATH_NOT_FOUND; }
            catch (System.IO.FileNotFoundException) { _logger("                  <-- ERROR_FILE_NOT_FOUND", LogLevel.debug); return -DokanNet.ERROR_FILE_NOT_FOUND; }
            catch (System.UnauthorizedAccessException) { _logger("                  <-- ERROR_ACCESS_DENIED", LogLevel.debug); return -DokanNet.ERROR_ACCESS_DENIED; }
            catch (Exception ex)
            { TraceFactory.InsertTrace(this, ex, filename, access, share, mode, options, info); return -1; }
        }

        public int OpenDirectory(String filename, DokanFileInfo info)
        {
            try
            {
                info.Context = _count++;
                if (filename == null)
                { filename = ""; }

                if (_rootselecthelper.GetDirectoryRoot(filename) >= 0)
                    return 0;
                else
                    return -DokanNet.ERROR_PATH_NOT_FOUND;
            }
            catch (Exception ex)
            { TraceFactory.InsertTrace(this, ex, filename, info); return -1; }
        }

        public int CreateDirectory(String filename, DokanFileInfo info)
        {
            try
            {
                if (_rootselecthelper.GetDirectoryRoot(filename) < 0)
                {
                    // clear cache (as filesystemwatcher isn't watching MERGE:\)
                    ClearCache();
                    Directory.CreateDirectory(_profile.Roots[_rootselectmanager.GetRootForCreateOP(filename, true)].RootPath + filename);
                    return 0;
                }
                else
                {
                    return -DokanNet.ERROR_ALREADY_EXISTS;
                }
            }
            catch (Exception ex)
            { TraceFactory.InsertTrace(this, ex, filename, info); return -1; }
        }

        // cleanup called after Windows releases the handle, delete operations here
        public int Cleanup(String filename, DokanFileInfo info)
        {
            _logger("FSOp: Cleanup: " + filename + ", Delete: " + info.DeleteOnClose + ", IsDirectory: " + info.IsDirectory, LogLevel.debug);
            try
            {

                if (info.IsDirectory)
                {
                    if (info.DeleteOnClose)
                    {
                        //we have to call delete on EVERY root, to make sure all the (duplicate) folders are gone
                        for (int i = 0; i < _profile.Roots.Count; i++)
                        {
                            if (Directory.Exists(_profile.Roots[i].RootPath + filename))
                            {

                                if (_profile.Roots[_rootselecthelper.GetFileRoot(filename)].ReadOnly)
                                { return -DokanNet.ERROR_ACCESS_DENIED; }
                                else
                                { Directory.Delete(_profile.Roots[i].RootPath + filename, true); }
                                // clear cache (as filesystemwatcher isn't watching MERGE:\)
                                ClearCache();
                            }
                        }
                    }
                }

                else
                {
                    if (info.Context != null)
                    {
                        if (_handles.ContainsKey((UInt64)info.Context))
                        {
                            FileStream asdf = _handles[(UInt64)info.Context];
                            _handles.Remove((UInt64)info.Context);
                            asdf.Close();
                        }
                    }

                    if (info.DeleteOnClose)
                    {
                        if (_profile.Roots[_rootselecthelper.GetFileRoot(filename)].ReadOnly)
                        { return -DokanNet.ERROR_ACCESS_DENIED; }
                        else
                        { File.Delete(_rootselecthelper.GetFilePath(filename)); }
                        // clear cache (as filesystemwatcher isn't watching MERGE:\)
                        ClearCache();
                    }

                }
            }
            catch (System.UnauthorizedAccessException)
            {
                return -DokanNet.ERROR_ACCESS_DENIED;
            }
            catch (Exception ex)
            { TraceFactory.InsertTrace(this, ex, filename, info); return -1; }
            return 0;
        }

        // make sure all handles are closed
        public int CloseFile(String filename, DokanFileInfo info)
        {
            _logger("FSOp: CloseFile: " + filename + ", IsDirectory: " + info.IsDirectory, LogLevel.debug);
            if (!info.IsDirectory && info.Context != null)
            {
                if (_handles.ContainsKey((UInt64)info.Context))
                {
                    FileStream asdf = _handles[(UInt64)info.Context];
                    _handles.Remove((UInt64)info.Context);
                    asdf.Close();
                }

            }
            return 0;
        }

        // we read the file, root selection is being done by _rootselecthelper.GetFilePath()
        public int ReadFile(String filename, Byte[] buffer, ref uint readBytes,
            long offset, DokanFileInfo info)
        {
            if (info.Context != null)
            {

                _logger("FSOp: ReadFile: " + filename, LogLevel.debug);
                try
                {
                    if (_handles.ContainsKey((UInt64)info.Context))
                    {
                        FileStream fs = _handles[(UInt64)info.Context];
                        fs.Seek(offset, SeekOrigin.Begin);
                        readBytes = (uint)fs.Read(buffer, 0, buffer.Length);
                    }
                    return 0;
                }
                catch (Exception ex)
                { TraceFactory.InsertTrace(this, ex, filename, buffer, readBytes, offset, info); return -1; }
            }
            else return -1;
        }

        public int WriteFile(String filename, Byte[] buffer,
            ref uint writtenBytes, long offset, DokanFileInfo info)
        {
            _logger("FSOp: WriteFile: " + filename + ", Offset:" + offset + ", Bufferlength: " + buffer.Length, LogLevel.debug);
            try
            {
                if (_handles.ContainsKey((UInt64)info.Context))
                {
                    FileStream fs = _handles[(UInt64)info.Context];
                    if (offset == -1) fs.Seek(0, SeekOrigin.End);
                    else fs.Seek(offset, SeekOrigin.Begin);
                    if (!_profile.Roots[_rootselecthelper.GetFileRoot(filename)].ReadOnly)
                    { fs.Write(buffer, 0, buffer.Length); }
                    else
                    { return -DokanNet.ERROR_ACCESS_DENIED; }
                    writtenBytes = (uint)buffer.Length;
                    ClearCache();
                }
                return 0;

            }
            catch (Exception ex)
            { TraceFactory.InsertTrace(this, ex, filename, buffer, writtenBytes, offset, info); return -1; }
        }

        public int FlushFileBuffers(String filename, DokanFileInfo info)
        {
            _logger("FSOp: FlushFileBuffers: " + filename, LogLevel.debug);
            try
            {
                if (_handles.ContainsKey((UInt64)info.Context))
                {
                    FileStream fs = _handles[(UInt64)info.Context];
                    fs.Flush();
                }
                return 0;

            }
            catch (Exception ex)
            { TraceFactory.InsertTrace(this, ex, filename, info); return -1; }
        }

        public int GetFileInformation(String filename, FileInformation fileinfo, DokanFileInfo info)
        {
            _logger("FSOp: GetFileInformation: " + filename, LogLevel.debug);
            try
            {
                // try cache first
                if (_cache_files.ContainsKey(filename))
                {
                    FileInformation f = _cache_files[filename];
                    fileinfo.Attributes = f.Attributes;
                    fileinfo.CreationTime = f.CreationTime;
                    fileinfo.LastAccessTime = f.LastAccessTime;
                    fileinfo.LastWriteTime = f.LastWriteTime;
                    fileinfo.Length = f.Length;
                    if (f.Length == 0) info.IsDirectory = true;
                    _logger("FSOp: GetFileInformation: Cached: " + filename, LogLevel.debug);
                    return 0;
                }

                // try file
                int fileroot = _rootselecthelper.GetFileRoot(filename);
                if (fileroot >= 0)
                {
                    info.IsDirectory = true;
                    string path = _rootselecthelper.GetFilePath(filename, fileroot);
                    FileInfo f = new FileInfo(path);

                    fileinfo.Attributes = f.Attributes;
                    fileinfo.CreationTime = f.CreationTime;
                    fileinfo.LastAccessTime = f.LastAccessTime;
                    fileinfo.LastWriteTime = f.LastWriteTime;
                    fileinfo.Length = f.Length;
                    return 0;
                }

                // try dir
                else
                {
                    int dirroot = _rootselecthelper.GetDirectoryRoot(filename);
                    if (dirroot >= 0)
                    {
                        string path = _rootselecthelper.GetDirectoryPath(filename, dirroot);
                        DirectoryInfo f = new DirectoryInfo(path);

                        fileinfo.Attributes = f.Attributes;
                        fileinfo.CreationTime = f.CreationTime;
                        fileinfo.LastAccessTime = f.LastAccessTime;
                        fileinfo.LastWriteTime = f.LastWriteTime;
                        fileinfo.Length = 0;// f.Length;
                        return 0;
                    }
                    else
                    {
                        return -1;
                    }
                }
            }
            catch (Exception ex)
            { TraceFactory.InsertTrace(this, ex, filename, fileinfo, info); return -1; }
        }

        public int FindFiles(String filename, ArrayList files, DokanFileInfo info)
        {
            _logger("FSOp: FindFiles: " + filename, LogLevel.debug);
            try
            {
                info.IsDirectory = true;
                // first we check if the cache contains our filename
                if (_cache.ContainsKey(filename))
                {
                    // cache is ok, return it
                    _logger("FSOp: FindFiles: Cache for " + filename + " available. Cache contains " + _cache[filename].Count + " entries.", LogLevel.debug);
                    if (_cache[filename].Count == 0) return -DokanNet.ERROR_FILE_NOT_FOUND;
                    files.AddRange(_cache[filename]);
                    return 0;
                }

                // cache seems to be out of date, let's create it and then return it
                int result = -1;
                for (int i = 0; i < _profile.Roots.Count; i++)
                {
                    if (Directory.Exists(_profile.Roots[i].RootPath + filename))
                    {
                        result = 0;
                        string path = _profile.Roots[i].RootPath + filename;
                        DirectoryInfo d = new DirectoryInfo(path);
                        try
                        {
                            FileSystemInfo[] entries = d.GetFileSystemInfos();
                            foreach (FileSystemInfo f in entries)
                            {
                                FileInformation fi = new FileInformation();
                                fi.Attributes = f.Attributes;
                                fi.CreationTime = f.CreationTime;
                                fi.LastAccessTime = f.LastAccessTime;
                                fi.LastWriteTime = f.LastWriteTime;
                                fi.Length = (f is DirectoryInfo) ? 0 : ((FileInfo)f).Length;
                                fi.FileName = f.Name;

                                // this stuff is pretty slow for large directories, maybe there is a better way to quickly check if a filename already exists on another root
                                bool alreadyadded = false;
                                foreach (FileInformation x in files)
                                {
                                    if (x.FileName.Equals(fi.FileName))
                                    {
                                        alreadyadded = true;
                                        break;
                                    }
                                }
                                if (!alreadyadded)
                                {
                                    files.Add(fi);
                                    _logger("      >> " + filename + fi.FileName, LogLevel.debug);
                                    lock (_cache_files_lock) { _cache_files.Add(filename + fi.FileName, fi); }
                                }
                            }
                        }
                        catch (System.UnauthorizedAccessException) { return -DokanNet.ERROR_ACCESS_DENIED; }
                    }
                }
                if (files.Count == 0) result = -DokanNet.ERROR_FILE_NOT_FOUND;

                lock (_cache_lock) { _cache.Add(filename, files); }
                _logger("FSOp: FindFiles: Cache for " + filename + " created. Cache contains " + files.Count + " entries.", LogLevel.debug);
                return result;
            }

            catch (Exception ex)
            { TraceFactory.InsertTrace(this, ex, filename, files, info); return -1; }
        }

        public int SetFileAttributes(String filename, FileAttributes attr, DokanFileInfo info)
        {
            _logger("FSOp: SetFileAttributes: " + filename, LogLevel.debug);
            try
            {
                if (_rootselecthelper.GetFileRoot(filename) >= 0)
                {
                    if (!_profile.Roots[_rootselecthelper.GetFileRoot(filename)].ReadOnly)
                    { File.SetAttributes(_rootselecthelper.GetFilePath(filename), attr); }
                    else { return -DokanNet.ERROR_ACCESS_DENIED; }
                    ClearCache();
                }
                //try directory -> do on every root
                else
                {
                    bool found = false;
                    for (int i = 0; i < _profile.Roots.Count; i++)
                    {
                        if (Directory.Exists(_profile.Roots[i].RootPath + filename))
                        {
                            found = true;
                            info.IsDirectory = true;
                            if (!_profile.Roots[_rootselecthelper.GetFileRoot(filename)].ReadOnly)
                            { File.SetAttributes(_profile.Roots[i].RootPath + filename, attr); }
                            else { return -DokanNet.ERROR_ACCESS_DENIED; }
                            ClearCache();
                        }
                    }
                    if (!found) return -DokanNet.ERROR_FILE_NOT_FOUND;
                }
                return 0;
            }
            catch (Exception ex)
            { TraceFactory.InsertTrace(this, ex, filename, attr, info); return -1; }
        }

        public int SetFileTime(String filename, DateTime ctime,
                DateTime atime, DateTime mtime, DokanFileInfo info)
        {
            _logger("FSOp: SetFileTime: " + filename, LogLevel.debug);
            try
            {
                // directory? -> change date on every root
                if (_rootselecthelper.GetDirectoryRoot(filename) >= 0)
                {
                    info.IsDirectory = true;
                    for (int i = 0; i < _profile.Roots.Count; i++)
                    {
                        // ignore 01.01.0001 00:00:00
                        if (ctime.Year > 1) if (Directory.Exists(_profile.Roots[i].RootPath + filename))
                            {
                                if (!_profile.Roots[_rootselecthelper.GetFileRoot(filename)].ReadOnly)
                                { Directory.SetCreationTime(_profile.Roots[i].RootPath + filename, ctime); }
                                else { return -DokanNet.ERROR_ACCESS_DENIED; }
                                ClearCache();
                            }
                        if (atime.Year > 1) if (Directory.Exists(_profile.Roots[i].RootPath + filename))
                            {
                                if (!_profile.Roots[_rootselecthelper.GetFileRoot(filename)].ReadOnly)
                                { Directory.SetLastAccessTime(_profile.Roots[i].RootPath + filename, atime); }
                                else { return -DokanNet.ERROR_ACCESS_DENIED; }
                                ClearCache();
                            }
                        if (mtime.Year > 1) if (Directory.Exists(_profile.Roots[i].RootPath + filename))
                            {
                                if (!_profile.Roots[_rootselecthelper.GetFileRoot(filename)].ReadOnly)
                                { Directory.SetLastWriteTime(_profile.Roots[i].RootPath + filename, mtime); }
                                else { return -DokanNet.ERROR_ACCESS_DENIED; }
                                ClearCache();
                            }
                    }
                }
                else
                {
                    string fn = _rootselecthelper.GetFilePath(filename);
                    // ignore 01.01.0001 00:00:00
                    if (ctime.Year > 1)
                    {
                        if (_profile.Roots[_rootselecthelper.GetFileRoot(filename)].ReadOnly)
                        { return -DokanNet.ERROR_ACCESS_DENIED; }
                        else
                        { File.SetCreationTime(fn, ctime); }
                        ClearCache();

                    }
                    if (atime.Year > 1)
                    {
                        if (_profile.Roots[_rootselecthelper.GetFileRoot(filename)].ReadOnly)
                        { return -DokanNet.ERROR_ACCESS_DENIED; }
                        else
                        { File.SetLastAccessTime(fn, atime); }
                        ClearCache();
                    }
                    if (mtime.Year > 1)
                    {
                        if (_profile.Roots[_rootselecthelper.GetFileRoot(filename)].ReadOnly)
                        { return -DokanNet.ERROR_ACCESS_DENIED; }
                        else
                        { File.SetLastWriteTime(fn, mtime); }
                        ClearCache();
                    }
                }

                return 0;
            }
            catch (Exception ex)
            { TraceFactory.InsertTrace(this, ex, filename, ctime, atime, mtime, info); return -1; }
        }

        public int DeleteFile(String filename, DokanFileInfo info)
        {
            _logger("FSOp: Delete File: " + filename, LogLevel.debug);

            if (_profile.Roots[_rootselecthelper.GetFileRoot(filename)].ReadOnly)
            { return -DokanNet.ERROR_ACCESS_DENIED; }
            else
            { return 0; }
        }

        public int DeleteDirectory(String filename, DokanFileInfo info)
        {
            _logger("FSOp: Delete Directory: " + filename, LogLevel.debug);
            info.IsDirectory = true;
            for (int i = 0; i < _profile.Roots.Count; i++)
            {
                if (Directory.Exists(_profile.Roots[i].RootPath + filename))
                {
                    String[] files = Directory.GetFiles(_profile.Roots[i].RootPath + filename);
                    if (files != null && files.Length > 0) return -DokanNet.ERROR_DIR_NOT_EMPTY;
                }
            }
            return 0;
        }

        // on moving we get the number of the root the file/directory is located, then we move to the same root
        // this is in confilict with read only roots
        public int MoveFile(String filename, String newname, bool replace, DokanFileInfo info)
        {
            _logger("FSOp: Move File: " + filename + " to " + newname + ", replace: " + replace, LogLevel.debug);
            try
            {
                // try file
                int sourceroot = _rootselecthelper.GetFileRoot(filename);
                if (sourceroot >= 0)
                {
                    if (filename.Equals(newname)) return 0;

                    int newroot = _rootselecthelper.GetFileRoot(newname);
                    if (newroot >= 0 && !replace) return -DokanNet.ERROR_ALREADY_EXISTS;

                    if (_profile.Roots[_rootselecthelper.GetFileRoot(newname)].ReadOnly)
                    { return -DokanNet.ERROR_ACCESS_DENIED; }
                    else
                    { if (replace) File.Delete(_profile.Roots[newroot].RootPath + newname); }

                    //maybe we are renaming a file to something like "somedir\file", then we have to make sure that "somedir\" exists on the slave.
                    if (_profile.Roots[_rootselecthelper.GetFileRoot(newname)].ReadOnly)
                    { return -DokanNet.ERROR_ACCESS_DENIED; }
                    else
                    { CreateNeededSubDirs(newname, sourceroot); }

                    if (_profile.Roots[_rootselecthelper.GetFileRoot(newname)].ReadOnly)
                    { return -DokanNet.ERROR_ACCESS_DENIED; }
                    else
                    { File.Move(_profile.Roots[sourceroot].RootPath + filename, _profile.Roots[sourceroot].RootPath + newname); }
                    ClearCache();
                }

                // try dir
                else
                {
                    // when moving dirs we have to make sure we rename all dirs in every root, else we end up with two dirs
                    for (int i = 0; i < _profile.Roots.Count; i++)
                    {
                        if (Directory.Exists(_profile.Roots[i].RootPath + filename))
                        {
                            //make sure the needed subdirs exist
                            if (_profile.Roots[_rootselecthelper.GetFileRoot(newname)].ReadOnly)
                            { return -DokanNet.ERROR_ACCESS_DENIED; }
                            else
                            { CreateNeededSubDirs(newname, i); }

                            if (_profile.Roots[_rootselecthelper.GetFileRoot(newname)].ReadOnly)
                            { return -DokanNet.ERROR_ACCESS_DENIED; }
                            else
                            { Directory.Move(_profile.Roots[i].RootPath + filename, _profile.Roots[i].RootPath + newname); }
                            // clear cache (filesystemwatcher doesnt notice!!!??!?!)
                            ClearCache();

                        }
                    }
                }
            }
            catch (System.UnauthorizedAccessException)
            {
                return -DokanNet.ERROR_ACCESS_DENIED;
            }
            catch (Exception ex)
            { TraceFactory.InsertTrace(this, ex, filename, newname, replace, info); return -1; }
            return 0;
        }

        public int SetEndOfFile(String filename, long length, DokanFileInfo info)
        {
            _logger("FSOp: SetEndOfFile: Filename: " + filename + ", length: " + length, LogLevel.debug);
            if (info.Context != null)
            {
                if (_handles.ContainsKey((UInt64)info.Context))
                {
                    FileStream fs = _handles[(UInt64)info.Context];
                    if (_profile.Roots[_rootselecthelper.GetFileRoot(filename)].ReadOnly)
                    { return -DokanNet.ERROR_ACCESS_DENIED; }
                    else
                    { fs.SetLength(length); }
                }
            }
            return 0;
        }

        public int LockFile(String filename, long offset, long length, DokanFileInfo info)
        {
            _logger("FSOp: LockFile", LogLevel.debug);
            return 0;
        }

        public int UnlockFile(String filename, long offset, long length, DokanFileInfo info)
        {
            _logger("FSOp: UnlockFile", LogLevel.debug);
            return 0;
        }

        public int GetDiskFreeSpace(ref ulong freeBytesAvailable, ref ulong totalBytes,
            ref ulong totalFreeBytes, DokanFileInfo info)
        {
            try
            {
                DiskSpaceInfo _info;

                _info = _rootselecthelper.GetSpaceInfo();

                freeBytesAvailable = (ulong)_info.FreeSpaceAvailable;
                totalBytes = (ulong)_info.TotalSize;
                totalFreeBytes = (ulong)_info.TotalFreeSpace;
                return 0;
            }
            catch (Exception ex)
            { TraceFactory.InsertTrace(this, ex, freeBytesAvailable, totalBytes, totalFreeBytes, info); return -1; }
        }

        public int Unmount(DokanFileInfo info)
        {
            _logger("FSOp: Unmount", LogLevel.debug);
            return 0;
        }

        private void CreateNeededSubDirs(string filename, int targetslave)
        {
            try
            {
                int from = targetslave;
                int to = targetslave;
                // -1 = all slaves
                if (targetslave == -1)
                {
                    from = 0;
                    to = _profile.Roots.Count - 1;
                }

                for (int i = from; i <= to; i++)
                {
                    string fullname = _profile.Roots[i].RootPath + filename;
                    string path = fullname.Remove(fullname.LastIndexOf('\\'));

                    if (!Directory.Exists(path))
                    {
                        Directory.CreateDirectory(path);
                    }
                }
            }
            catch (Exception ex)
            { TraceFactory.InsertTrace(this, ex, filename, targetslave); throw; }
        }

    }
}
